// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:build linux 

package syscall

import (
	"internal/itoa"
	"runtime"
	"unsafe"
)

// SysProcIDMap保存用于linux中用户名称空间的容器ID到主机ID的映射。
// 参见用户名称空间（7）。
type SysProcIDMap struct {
	ContainerID int // 容器ID.
	HostID      int // 主机ID.
	Size        int // 大小。
}

type SysProcAttr struct {
	Chroot     string      // Chroot。
	Credential *Credential // 凭证。
	// Ptrace告诉孩子打电话给Ptrace（Ptrace_TRACEME）。
	// 调用运行时。LockOSThread在使用该集合启动进程之前，
	// 并且在完成PtraceSyscall调用之前不要调用UnlockOSThread。
	Ptrace bool
	Setsid bool // 创建会话。
	// Setpgid将子进程的进程组ID设置为Pgid，
	// 或者，如果Pgid==0，则设置为新子进程的进程ID。
	Setpgid bool
	// SETCTY将子进程的控制终端设置为
	// 文件描述符Ctty。Ctty必须是子进程中的描述符编号
	// ：procatr的索引。文件夹。
	// 只有当Setsid为true时，这才有意义。
	Setctty bool
	Noctty  bool // 从控制终端分离fd 0 
	Ctty    int  // 控制TTY fd 
	// 前台将子进程组放置在前台。
	// 这意味着Setpgid。Ctty字段必须设置为
	// 控制TTY的描述符。
	// 与Setctty不同，在本例中，Ctty必须是父进程中的描述符
	// 编号。
	Foreground   bool
	Pgid         int            // 如果设置为Setpgid，则为子进程组ID。
	Pdeathsig    Signal         // 当其父进程死亡（仅限Linux和FreeBSD）时进程将获得的信号
	Cloneflags   uintptr        // 克隆调用的标志（仅限Linux）
	Unshareflags uintptr        // 未共享调用的标志（仅限Linux）
	UidMappings  []SysProcIDMap // 用户名称空间的用户ID映射。
	GidMappings  []SysProcIDMap // 用户名称空间的组ID映射。
	// GidMappingsEnableSetgroups启用setgroups系统调用。
	// 如果为false，则子进程的setgroups系统调用将被禁用。
	// 如果GidMappings==nil，则此参数不可用。否则，对于无特权的
	// 用户，应将其设置为false，以便映射工作。
	GidMappingsEnableSetgroups bool
	AmbientCaps                []uintptr // 环境功能（仅限Linux）
}

var (
	none  = [...]byte{'n', 'o', 'n', 'e', 0}
	slash = [...]byte{'/', 0}
)

// 在运行时包中实现。
func runtime_BeforeFork()
func runtime_AfterFork()
func runtime_AfterForkInChild()

// 分叉，将fd复制到0上。。len（fd）和exec（argv0、argvv、envv）在child中。
// 如果dup或exec失败，请将errno错误写入管道。
// （管道在exec上关闭，因此如果exec成功，它将关闭。）
// 在子函数中，此函数不能获取任何锁，因为
// 它们可能在分叉时已被锁定。这意味着
// 没有重新调度，没有malloc调用，也没有新的堆栈段。
// 出于同样的原因，编译器不会对其进行测试。
// 对RawSyscall的调用没有问题，因为它们是不会增加堆栈的程序集
// 函数。
// 开始：norace 
func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) {
	// 设置并分叉。如果有错误，它会立即返回到父级或
	// 中。
	r1, err1, p, locked := forkAndExecInChild1(argv0, argv, envv, chroot, dir, attr, sys, pipe)
	if locked {
		runtime_AfterFork()
	}
	if err1 != 0 {
		return 0, err1
	}

	// 父母；return PID 
	pid = int(r1)

	if sys.UidMappings != nil || sys.GidMappings != nil {
		Close(p[0])
		var err2 Errno
		// uid/gid映射将在用户
		// 名称空间的fork和unshare（2）之后写入。
		if sys.Unshareflags&CLONE_NEWUSER == 0 {
			if err := writeUidGidMappings(pid, sys); err != nil {
				err2 = err.(Errno)
			}
		}
		RawSyscall(SYS_WRITE, uintptr(p[1]), uintptr(unsafe.Pointer(&err2)), unsafe.Sizeof(err2))
		Close(p[1])
	}

	return pid, 0
}

const _LINUX_CAPABILITY_VERSION_3 = 0x20080522

type capHeader struct {
	version uint32
	pid     int32
}

type capData struct {
	effective   uint32
	permitted   uint32
	inheritable uint32
}
type caps struct {
	hdr  capHeader
	data [2]capData
}

// 请参阅linux/capability中的CAP_TO_索引。h:
func capToIndex(cap uintptr) uintptr { return cap >> 5 }

// 请参阅linux/capability中的CAP_TO_掩码。h:
func capToMask(cap uintptr) uint32 { return 1 << uint(cap&31) }

// forkAndExecInChild1实现forkAndExecInChild1的主体，直到
// 父级的后分叉路径。这是一个单独的函数，因此如果我们使用
// vWork，我们可以分离子级和父级的堆栈帧。
// 
// 这是go:noinline，因为重点是将This和forkAndExecInChild的堆栈帧
// 分开。
// 
// go:noinline 
// go:norace 
func forkAndExecInChild1(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (r1 uintptr, err1 Errno, p [2]int, locked bool) {
	// 在linux/prctl中定义。h从Linux 4.3开始。
	const (
		PR_CAP_AMBIENT       = 0x2f
		PR_CAP_AMBIENT_RAISE = 0x2
	)

	// vWork要求子级不接触父级的任何
	// 活动堆栈帧。因此，子级在这个堆栈帧中执行所有的post fork 
	// 处理，并且永远不会返回，而
	// 父级立即从这个帧返回，并且在外部帧中执行所有
	// post fork处理。
	// 如果任何
	// 声明需要堆分配（例如err1），请在顶部声明所有变量。
	var (
		err2                      Errno
		nextfd                    int
		i                         int
		caps                      caps
		fd1                       uintptr
		puid, psetgroups, pgid    []byte
		uidmap, setgroups, gidmap []byte
	)

	if sys.UidMappings != nil {
		puid = []byte("/proc/self/uid_map\000")
		uidmap = formatIDMappings(sys.UidMappings)
	}

	if sys.GidMappings != nil {
		psetgroups = []byte("/proc/self/setgroups\000")
		pgid = []byte("/proc/self/gid_map\000")

		if sys.GidMappingsEnableSetgroups {
			setgroups = []byte("allow\000")
		} else {
			setgroups = []byte("deny\000")
		}
		gidmap = formatIDMappings(sys.GidMappings)
	}

	// 记录父PID，以便子PID可以测试它是否已死亡。
	ppid, _ := rawSyscallNoError(SYS_GETPID, 0, 0, 0)

	// 注意下面的fds洗牌的副作用。
	// 确保nextfd超出任何当前打开的文件，以便
	// 我们不能冒着重写其中任何一个的风险。
	fd := make([]int, len(attr.Files))
	nextfd = len(attr.Files)
	for i, ufd := range attr.Files {
		if nextfd < int(ufd) {
			nextfd = int(ufd)
		}
		fd[i] = int(ufd)
	}
	nextfd++

	// 为
	// 同步用户ID/组ID映射的写入，为父子通信分配另一个管道。
	if sys.UidMappings != nil || sys.GidMappings != nil {
		if err := forkExecPipe(p[:]); err != nil {
			err1 = err.(Errno)
			return
		}
	}

	// 即将呼叫fork。
	// 不再分配或调用非汇编函数。
	runtime_BeforeFork()
	locked = true
	switch {
	case sys.Cloneflags&CLONE_NEWUSER == 0 && sys.Unshareflags&CLONE_NEWUSER == 0:
		r1, err1 = rawVforkSyscall(SYS_CLONE, uintptr(SIGCHLD|CLONE_VFORK|CLONE_VM)|sys.Cloneflags)
	case runtime.GOARCH == "s390x":
		r1, _, err1 = RawSyscall6(SYS_CLONE, 0, uintptr(SIGCHLD)|sys.Cloneflags, 0, 0, 0, 0)
	default:
		r1, _, err1 = RawSyscall6(SYS_CLONE, uintptr(SIGCHLD)|sys.Cloneflags, 0, 0, 0, 0, 0)
	}
	if err1 != 0 || r1 != 0 {
		// 如果我们在父对象中，我们必须立即返回
		// 因此我们与子对象不在同一堆栈帧中。
		// 这最多可以使用返回PC，子
		// 将不会修改返回PC，以及
		// rawVforkSyscall的结果，该结果必须是在替换子
		// 后编写的。
		return
	}

	// Fork成功了，现在是child。

	// 稍后启用“保持能力”标志以设置环境能力。
	if len(sys.AmbientCaps) > 0 {
		_, _, err1 = RawSyscall6(SYS_PRCTL, PR_SET_KEEPCAPS, 1, 0, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 等待写入用户ID/组ID映射。
	if sys.UidMappings != nil || sys.GidMappings != nil {
		if _, _, err1 = RawSyscall(SYS_CLOSE, uintptr(p[1]), 0, 0); err1 != 0 {
			goto childerror
		}
		r1, _, err1 = RawSyscall(SYS_READ, uintptr(p[0]), uintptr(unsafe.Pointer(&err2)), unsafe.Sizeof(err2))
		if err1 != 0 {
			goto childerror
		}
		if r1 != unsafe.Sizeof(err2) {
			err1 = EINVAL
			goto childerror
		}
		if err2 != 0 {
			err1 = err2
			goto childerror
		}
	}

	// 会话ID 
	if sys.Setsid {
		_, _, err1 = RawSyscall(SYS_SETSID, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 设置进程组
	if sys.Setpgid || sys.Foreground {
		// 将子进程放入进程组。
		_, _, err1 = RawSyscall(SYS_SETPGID, 0, uintptr(sys.Pgid), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	if sys.Foreground {
		pgrp := int32(sys.Pgid)
		if pgrp == 0 {
			r1, _ = rawSyscallNoError(SYS_GETPID, 0, 0, 0)

			pgrp = int32(r1)
		}

		// 将流程组放在前台。
		_, _, err1 = RawSyscall(SYS_IOCTL, uintptr(sys.Ctty), uintptr(TIOCSPGRP), uintptr(unsafe.Pointer(&pgrp)))
		if err1 != 0 {
			goto childerror
		}
	}

	// 恢复信号掩码。我们在TIOCSPGRP之后这样做是为了避免
	// 让内核向进程组发送SIGTTOU信号。
	runtime_AfterForkInChild()

	// 取消共享
	if sys.Unshareflags != 0 {
		_, _, err1 = RawSyscall(SYS_UNSHARE, sys.Unshareflags, 0, 0)
		if err1 != 0 {
			goto childerror
		}

		if sys.Unshareflags&CLONE_NEWUSER != 0 && sys.GidMappings != nil {
			dirfd := int(_AT_FDCWD)
			if fd1, _, err1 = RawSyscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(&psetgroups[0])), uintptr(O_WRONLY), 0, 0, 0); err1 != 0 {
				goto childerror
			}
			r1, _, err1 = RawSyscall(SYS_WRITE, uintptr(fd1), uintptr(unsafe.Pointer(&setgroups[0])), uintptr(len(setgroups)))
			if err1 != 0 {
				goto childerror
			}
			if _, _, err1 = RawSyscall(SYS_CLOSE, uintptr(fd1), 0, 0); err1 != 0 {
				goto childerror
			}

			if fd1, _, err1 = RawSyscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(&pgid[0])), uintptr(O_WRONLY), 0, 0, 0); err1 != 0 {
				goto childerror
			}
			r1, _, err1 = RawSyscall(SYS_WRITE, uintptr(fd1), uintptr(unsafe.Pointer(&gidmap[0])), uintptr(len(gidmap)))
			if err1 != 0 {
				goto childerror
			}
			if _, _, err1 = RawSyscall(SYS_CLOSE, uintptr(fd1), 0, 0); err1 != 0 {
				goto childerror
			}
		}

		if sys.Unshareflags&CLONE_NEWUSER != 0 && sys.UidMappings != nil {
			dirfd := int(_AT_FDCWD)
			if fd1, _, err1 = RawSyscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(&puid[0])), uintptr(O_WRONLY), 0, 0, 0); err1 != 0 {
				goto childerror
			}
			r1, _, err1 = RawSyscall(SYS_WRITE, uintptr(fd1), uintptr(unsafe.Pointer(&uidmap[0])), uintptr(len(uidmap)))
			if err1 != 0 {
				goto childerror
			}
			if _, _, err1 = RawSyscall(SYS_CLOSE, uintptr(fd1), 0, 0); err1 != 0 {
				goto childerror
			}
		}

		// Linux中的取消共享系统调用不取消共享装载点
		// 装载时使用--shared。Systemd挂载/使用--shared。要了解
		// 关于这个方法的利弊的详细讨论，请参阅debian bug 739593。
		// 取消共享的Go模型更像计划9，您要求
		// 取消共享，名称空间无条件取消共享。
		// 为了使这个模型发挥作用，我们必须进一步将其标记为“女士私人”。
		// 这是标准的unshare命令所做的。
		if sys.Unshareflags&CLONE_NEWNS == CLONE_NEWNS {
			_, _, err1 = RawSyscall6(SYS_MOUNT, uintptr(unsafe.Pointer(&none[0])), uintptr(unsafe.Pointer(&slash[0])), 0, MS_REC|MS_PRIVATE, 0, 0)
			if err1 != 0 {
				goto childerror
			}
		}
	}

	// Chroot 
	if chroot != nil {
		_, _, err1 = RawSyscall(SYS_CHROOT, uintptr(unsafe.Pointer(chroot)), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 用户和组
	if cred := sys.Credential; cred != nil {
		ngroups := uintptr(len(cred.Groups))
		groups := uintptr(0)
		if ngroups > 0 {
			groups = uintptr(unsafe.Pointer(&cred.Groups[0]))
		}
		if !(sys.GidMappings != nil && !sys.GidMappingsEnableSetgroups && ngroups == 0) && !cred.NoSetGroups {
			_, _, err1 = RawSyscall(_SYS_setgroups, ngroups, groups, 0)
			if err1 != 0 {
				goto childerror
			}
		}
		_, _, err1 = RawSyscall(sys_SETGID, uintptr(cred.Gid), 0, 0)
		if err1 != 0 {
			goto childerror
		}
		_, _, err1 = RawSyscall(sys_SETUID, uintptr(cred.Uid), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	if len(sys.AmbientCaps) != 0 {
		// 在4.3内核中添加了环境功能，
		// 因此始终使用_LINUX_CAPABILITY_VERSION_3是安全的。
		caps.hdr.version = _LINUX_CAPABILITY_VERSION_3

		if _, _, err1 := RawSyscall(SYS_CAPGET, uintptr(unsafe.Pointer(&caps.hdr)), uintptr(unsafe.Pointer(&caps.data[0])), 0); err1 != 0 {
			goto childerror
		}

		for _, c := range sys.AmbientCaps {
			// 将c功能添加到允许和可继承的功能掩码，
			// 否则我们将无法将其添加到环境功能掩码。
			caps.data[capToIndex(c)].permitted |= capToMask(c)
			caps.data[capToIndex(c)].inheritable |= capToMask(c)
		}

		if _, _, err1 := RawSyscall(SYS_CAPSET, uintptr(unsafe.Pointer(&caps.hdr)), uintptr(unsafe.Pointer(&caps.data[0])), 0); err1 != 0 {
			goto childerror
		}

		for _, c := range sys.AmbientCaps {
			_, _, err1 = RawSyscall6(SYS_PRCTL, PR_CAP_AMBIENT, uintptr(PR_CAP_AMBIENT_RAISE), c, 0, 0, 0)
			if err1 != 0 {
				goto childerror
			}
		}
	}

	// Chdir 
	if dir != nil {
		_, _, err1 = RawSyscall(SYS_CHDIR, uintptr(unsafe.Pointer(dir)), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 父母死亡信号
	if sys.Pdeathsig != 0 {
		_, _, err1 = RawSyscall6(SYS_PRCTL, PR_SET_PDEATHSIG, uintptr(sys.Pdeathsig), 0, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}

		// 如果父母已经死亡，则发出自我信号。在极少数情况下，这可能会导致
		// 重复信号，但当
		// 使用SIGKILL时，这并不重要。
		r1, _ = rawSyscallNoError(SYS_GETPPID, 0, 0, 0)
		if r1 != ppid {
			pid, _ := rawSyscallNoError(SYS_GETPID, 0, 0, 0)
			_, _, err1 := RawSyscall(SYS_KILL, pid, uintptr(sys.Pdeathsig), 0)
			if err1 != 0 {
				goto childerror
			}
		}
	}

	// Pass 1:寻找fd[i]<i，并将其移到len（fd）上方
	// 这样Pass 2就不会踩到以后需要的fd上。
	if pipe < nextfd {
		_, _, err1 = RawSyscall(SYS_DUP3, uintptr(pipe), uintptr(nextfd), O_CLOEXEC)
		if err1 != 0 {
			goto childerror
		}
		pipe = nextfd
		nextfd++
	}
	for i = 0; i < len(fd); i++ {
		if fd[i] >= 0 && fd[i] < int(i) {
			if nextfd == pipe { // 不要踩在管道上
				nextfd++
			}
			_, _, err1 = RawSyscall(SYS_DUP3, uintptr(fd[i]), uintptr(nextfd), O_CLOEXEC)
			if err1 != 0 {
				goto childerror
			}
			fd[i] = nextfd
			nextfd++
		}
	}

	// 将2:dup fd[i]传递到i上。
	for i = 0; i < len(fd); i++ {
		if fd[i] == -1 {
			RawSyscall(SYS_CLOSE, uintptr(i), 0, 0)
			continue
		}
		if fd[i] == int(i) {
			// dup2（i，i）在Linux上不会清除close on exec标志，
			// 在其他地方可能也不会。
			_, _, err1 = RawSyscall(fcntl64Syscall, uintptr(fd[i]), F_SETFD, 0)
			if err1 != 0 {
				goto childerror
			}
			continue
		}
		// 新的fd不是在exec上创建的，
		// 这正是我们想要的。
		_, _, err1 = RawSyscall(SYS_DUP3, uintptr(fd[i]), uintptr(i), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 按照惯例，我们不会关闭我们的fds，所以如果len（fd）<3，根据需要关闭0,1,2。
	// 知道自己继承了fds>=3的程序需要
	// 才能在exec上关闭它们。
	for i = len(fd); i < 3; i++ {
		RawSyscall(SYS_CLOSE, uintptr(i), 0, 0)
	}

	// 将fd 0从tty中分离
	if sys.Noctty {
		_, _, err1 = RawSyscall(SYS_IOCTL, 0, uintptr(TIOCNOTTY), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 将控制tty设置为Ctty 
	if sys.Setctty {
		_, _, err1 = RawSyscall(SYS_IOCTL, uintptr(sys.Ctty), uintptr(TIOCSCTTY), 1)
		if err1 != 0 {
			goto childerror
		}
	}

	// 根据请求启用跟踪。
	// 在exec之前执行此操作，这样我们就不会不必要地跟踪运行时
	// 在fork之后进行设置。见第21428期。
	if sys.Ptrace {
		_, _, err1 = RawSyscall(SYS_PTRACE, uintptr(PTRACE_TRACEME), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 执行时间到了。
	_, _, err1 = RawSyscall(SYS_EXECVE,
		uintptr(unsafe.Pointer(argv0)),
		uintptr(unsafe.Pointer(&argv[0])),
		uintptr(unsafe.Pointer(&envv[0])))

childerror:
	// 在管道上发送错误代码
	RawSyscall(SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1))
	for {
		RawSyscall(SYS_EXIT, 253, 0, 0)
	}
}

// 尝试打开在两个文件描述符上都设置了O_CLOEXEC的管道。
func forkExecPipe(p []int) (err error) {
	return Pipe2(p, O_CLOEXEC)
}

func formatIDMappings(idMap []SysProcIDMap) []byte {
	var data []byte
	for _, im := range idMap {
		data = append(data, []byte(itoa.Itoa(im.ContainerID)+" "+itoa.Itoa(im.HostID)+" "+itoa.Itoa(im.Size)+"\n")...)
	}
	return data
}

// writeIDMappings将用户命名空间用户ID或组ID映射写入指定路径。
func writeIDMappings(path string, idMap []SysProcIDMap) error {
	fd, err := Open(path, O_RDWR, 0)
	if err != nil {
		return err
	}

	if _, err := Write(fd, formatIDMappings(idMap)); err != nil {
		Close(fd)
		return err
	}

	if err := Close(fd); err != nil {
		return err
	}

	return nil
}

// 如果enable为false，writeSetgroups写入/proc/PID/setgroups“deny”；如果enable为true，则写入“allow”。
// 从内核3.19开始，这是必需的，因为没有
// 禁用setgroups（）系统调用，就无法编写gid_映射。
func writeSetgroups(pid int, enable bool) error {
	sgf := "/proc/" + itoa.Itoa(pid) + "/setgroups"
	fd, err := Open(sgf, O_RDWR, 0)
	if err != nil {
		return err
	}

	var data []byte
	if enable {
		data = []byte("allow")
	} else {
		data = []byte("deny")
	}

	if _, err := Write(fd, data); err != nil {
		Close(fd)
		return err
	}

	return Close(fd)
}

// writeUidGidMappings为进程写入用户名称空间的用户ID和组ID映射
// 并从父进程调用。
func writeUidGidMappings(pid int, sys *SysProcAttr) error {
	if sys.UidMappings != nil {
		uidf := "/proc/" + itoa.Itoa(pid) + "/uid_map"
		if err := writeIDMappings(uidf, sys.UidMappings); err != nil {
			return err
		}
	}

	if sys.GidMappings != nil {
		// 如果内核太旧，无法支持/proc/PID/setgroups，writeSetGroups将返回enoint；这没关系。
		if err := writeSetgroups(pid, sys.GidMappingsEnableSetgroups); err != nil && err != ENOENT {
			return err
		}
		gidf := "/proc/" + itoa.Itoa(pid) + "/gid_map"
		if err := writeIDMappings(gidf, sys.GidMappings); err != nil {
			return err
		}
	}

	return nil
}
